﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;
using System.Threading;
using System.Web;
/// <summary>The EnumExtensions class</summary>
/// <created author="laurentiu.macovei" date="Thu, 10 Mar 2011 01:08:46 GMT"/>
public static class EnumExtensions
{

	#region Fields


	#region Const

	private static readonly MethodInfo GetNamesAndValuesMethod = typeof(EnumExtensions).GetMethod("GetNamesAndValues", BindingFlags.NonPublic | BindingFlags.Static);
	private static readonly MethodInfo GetNamesAndValuesMethodDictionary = typeof(EnumExtensions).GetMethod("GetNamesAndValuesDictionary", BindingFlags.NonPublic | BindingFlags.Static);
	public static readonly ConcurrentDictionary<int, CultureInfo> Cultures = new ConcurrentDictionary<int, CultureInfo>();

	#endregion Const


	#endregion Fields


	#region Util Methods

	/// <summary>Loads or initializes the current UI culture</summary>
	/// <param name="culture">The culture info you are looking for</param>
	/// <param name="cultureInfoId">The cultureId to be retrieved</param>
	/// <created author="laurentiu.macovei" date="Thu, 14 Apr 2011 05:35:43 GMT"/>
	private static CultureInfo LoadOrInitializeCulture<EnumType>(CultureInfo culture, int cultureInfoId)
		where EnumType : struct, IComparable, IFormattable, IConvertible
	{
		CultureInfo cultureInfo;
		if (!Cultures.TryGetValue(cultureInfoId, out cultureInfo))
		{
			lock (Cultures)
			{
				if (!Cultures.TryGetValue(cultureInfoId, out cultureInfo))
				{
					AssemblyName assemblyName = new AssemblyName();
					assemblyName.Name = "tmpAssembly";
					AssemblyBuilder assemblyBuilder = Thread.GetDomain().DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
					ModuleBuilder module = assemblyBuilder.DefineDynamicModule("tmpModule");

					// create a new type builder
					TypeBuilder typeBuilder = module.DefineType(culture.ThreeLetterISOLanguageName + "SpecificCulture", TypeAttributes.Public | TypeAttributes.Class, typeof(CultureInfo));
					// Declaring method builder
					// Method attributes
					System.Reflection.MethodAttributes methodAttributes =
						  System.Reflection.MethodAttributes.Public
						| System.Reflection.MethodAttributes.HideBySig;
					var method = typeBuilder.DefineConstructor(methodAttributes, CallingConventions.Standard, null);
					// Preparing Reflection instances
					ConstructorInfo ctor1 = typeof(CultureInfo).GetConstructor(
						BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic,
						null,
						new Type[]{
            typeof(Int32)
            },
						null
						);
					// Adding parameters
					ILGenerator gen = method.GetILGenerator();
					// Writing body
					gen.Emit(OpCodes.Ldarg_0);
					gen.Emit(OpCodes.Ldc_I4, cultureInfoId);
					gen.Emit(OpCodes.Call, ctor1);
					gen.Emit(OpCodes.Nop);
					gen.Emit(OpCodes.Nop);
					gen.Emit(OpCodes.Nop);
					gen.Emit(OpCodes.Ret);
					// finished
					typeBuilder.DefineField("Instance", typeof(CultureInfo), FieldAttributes.Static | FieldAttributes.Public);
					var type = typeBuilder.CreateType();
					var value = Activator.CreateInstance(type) as CultureInfo;
					type.GetField("Instance", BindingFlags.Public | BindingFlags.Static).SetValue(null, value);
					cultureInfo = value;
					Cultures[cultureInfoId] = cultureInfo;
				}
			}
		}
		if (EnumInfo<EnumType>.GetNamesAndValuesMethod == null || !EnumInfo<EnumType>.GetNamesAndValuesMethod.ContainsKey(cultureInfoId))
			lock (Cultures)
				if (EnumInfo<EnumType>.GetNamesAndValuesMethod == null || !EnumInfo<EnumType>.GetNamesAndValuesMethod.ContainsKey(cultureInfoId))
				{
					EnumInfo<EnumType>.GetNamesAndValuesMethod = EnumInfo<EnumType>.GetNamesAndValuesMethod ?? new Dictionary<int, MethodInfo>();
					var type = cultureInfo.GetType();
					var m = GetNamesAndValuesMethod.MakeGenericMethod(typeof(EnumType), type);
					EnumInfo<EnumType>.GetNamesAndValuesMethod[cultureInfoId] = m;
					m.Invoke(null, null);
				}
		if (EnumInfo<EnumType>.GetNamesAndValuesMethodDictionary == null || !EnumInfo<EnumType>.GetNamesAndValuesMethodDictionary.ContainsKey(cultureInfoId))
			lock (Cultures)
				if (EnumInfo<EnumType>.GetNamesAndValuesMethodDictionary == null || !EnumInfo<EnumType>.GetNamesAndValuesMethodDictionary.ContainsKey(cultureInfoId))
				{
					EnumInfo<EnumType>.GetNamesAndValuesMethodDictionary = EnumInfo<EnumType>.GetNamesAndValuesMethodDictionary ?? new Dictionary<int, MethodInfo>();
					var type = cultureInfo.GetType();
					var m = GetNamesAndValuesMethodDictionary.MakeGenericMethod(typeof(EnumType), type);
					EnumInfo<EnumType>.GetNamesAndValuesMethodDictionary[cultureInfoId] = m;
				}
		return cultureInfo;
	}

	/// <summary>Returns the EnumStorage&lt;EnumType, Culture&gt;.NamesAndValues</summary>
	/// <created author="laurentiu.macovei" date="Thu, 14 Apr 2011 05:35:43 GMT"/>
	private static List<EnumInfo<EnumType>> GetNamesAndValues<EnumType, Culture>()
		where EnumType : struct, IComparable, IFormattable, IConvertible
		where Culture : CultureInfo
	{
		return EnumStorage<EnumType, Culture>.NamesAndValues;
	}

	/// <summary>Returns the EnumStorage&lt;EnumType, Culture&gt;.NamesAndValuesDictionary</summary>
	/// <created author="laurentiu.macovei" date="Thu, 14 Apr 2011 05:35:43 GMT"/>
	private static Dictionary<EnumType, EnumInfo<EnumType>> GetNamesAndValuesDictionary<EnumType, Culture>()
		where EnumType : struct, IComparable, IFormattable, IConvertible
		where Culture : CultureInfo
	{
		return EnumStorage<EnumType, Culture>.NamesAndValuesDictionary;
	}


	#endregion Util Methods


	#region Business Methods

	/// <summary>Returns a flags enum from a list of values</summary>
	/// <param name="type">The type we want to parse the enum values from</param>
	/// <param name="source">The list of the values</param>
	/// <param name="formKey">The key found in the list of values to identify the values</param>
	/// <created author="laurentiu.macovei" date="Mon, 28 Mar 2011 22:39:38 GMT"/>
	public static IConvertible ParseToEnumFlag(this Type type, NameValueCollection source, string formKey)
	{
		if (!type.IsEnum)
			return null;
		//MVC 'helpfully' parses the checkbox into a comma-delimited list. We pull that out and sum the values after parsing it back into the enum.
		return (IConvertible)Enum.ToObject(type, (source.Get(formKey) ?? string.Empty).Split(StringSplitOptions.RemoveEmptyEntries, ',').Select(i => long.Parse(i)).Sum());
	}

	/// <summary>Converts the given enum to json</summary>
	/// <param name="enum"></param>
	/// <returns></returns>
	/// <created author="tudor.dumitriu" date="Sun, 27 Mar 2011 16:30:32 GMT"/>
	public static string ToObjectJson<T>(this T @enum) //ext method
		where T : struct, IComparable, IFormattable, IConvertible
	{
		var type = typeof(T);
		var values = ((T[])Enum.GetValues(type)).Select(e => e.ToInt64(CultureInfo.CurrentCulture.NumberFormat)).ToArray();

		var result = type.Name + ": {" +
			string.Join(",", values.Select(e => Enum.GetName(type, e) + ":" + e).ToArray())
					+ " }";
		return result;
	}

	/// <summary>Returns an IEnumerable with all the enum info for this enum type</summary>
	/// <param name="enumType">The enum value for which the type inference would work</param>
	/// <created author="laurentiu.macovei" date="Thu, 14 Apr 2011 05:35:42 GMT"/>
	public static IEnumerable<EnumInfo<EnumType>> GetEnumerator<EnumType>(this EnumType enumType)
		where EnumType : struct, IComparable, IFormattable, IConvertible
	{
		var culture = Thread.CurrentThread.CurrentUICulture;
		var cultureInfoId = culture.LCID;
		CultureInfo cultureInfo = LoadOrInitializeCulture<EnumType>(culture, cultureInfoId);

		var result = EnumInfo<EnumType>.GetNamesAndValuesMethod[cultureInfoId].Invoke(null, null);
		var r = result as IEnumerable<EnumInfo<EnumType>>;
		return r;
	}

	/// <summary>Returns the EnumInfo for a particular enum value</summary>
	/// <param name="enum">The enum value to return the EnumInfo for</param>
	/// <created author="laurentiu.macovei" date="Thu, 14 Apr 2011 05:35:42 GMT"/>
	public static EnumInfo<EnumType> GetEnumInfo<EnumType>(this EnumType @enum)
		where EnumType : struct, IComparable, IFormattable, IConvertible
	{
		var culture = Thread.CurrentThread.CurrentUICulture;
		CultureInfo cultureInfo = LoadOrInitializeCulture<EnumType>(culture, culture.LCID);

		var dictionary = EnumInfo<EnumType>.GetNamesAndValuesMethodDictionary[culture.LCID].Invoke(null, null) as Dictionary<EnumType, EnumInfo<EnumType>>;
		EnumInfo<EnumType> result;
		dictionary.TryGetValue(@enum, out result);
		return result;
	}

	/// <summary>Returns the localized value of the DisplayAttribute or DescriptionAttribute or the enum.ToString()</summary>
	/// <param name="enum">The enum for which to return </param>
	/// <remarks>
	/// You need to decorate your enum with attributes like these: 
	/// [Display(Description = "MyResourceName", ResourceType = typeof(MyResourceTypeName))] 
	/// or 
	/// [Description("My custom description text - not localizable")] 
	///  
	/// Please NOTE that the value is cached per culture info and enum value for faster subsequent access. 
	/// </remarks>
	/// <returns>Either string or HTMLString depending on how it was defined in the resource i.e. starting with HTML: or not</returns>
	/// <created author="laurentiu.macovei" date="Sun, 20 Mar 2011 18:03:42 GMT"/>
	[DebuggerStepThrough]
	public static dynamic AsHtmlString<T>(this T @enum) //ext method
		where T : struct, IComparable, IFormattable, IConvertible
	{
		return @enum.GetEnumInfo().HtmlDescription;
	}

	/// <summary>Returns the localized value of the DisplayAttribute or DescriptionAttribute or the enum.ToString()</summary>
	/// <param name="enum">The enum for which to return </param>
	/// <remarks>
	/// You need to decorate your enum with attributes like these: 
	/// [Display(Description = "MyResourceName", ResourceType = typeof(MyResourceTypeName))] 
	/// or 
	/// [Description("My custom description text - not localizable")] 
	///  
	/// Please NOTE that the value is cached per culture info and enum value for faster subsequent access. 
	/// </remarks>
	/// <created author="laurentiu.macovei" date="Thu, 10 Mar 2011 01:08:46 GMT"/>
	[DebuggerStepThrough]
	public static string AsString<T>(this T @enum)
		where T : struct, IComparable, IFormattable, IConvertible
	{
		return @enum.GetEnumInfo().Description;
	}


	#endregion Business Methods


	#region EnumInfo Class
	/// <summary>The EnumInfo class</summary>
	/// <created author="laurentiu.macovei" date="Thu, 14 Apr 2011 05:35:43 GMT"/>
	public class EnumInfo<EnumType>
			where EnumType : struct, IComparable, IFormattable, IConvertible
	{

		#region Fields


		#region Static

		internal static Dictionary<int, MethodInfo> GetNamesAndValuesMethod;
		internal static Dictionary<int, MethodInfo> GetNamesAndValuesMethodDictionary;

		#endregion Static


		#endregion Fields


		#region Constructors

		/// <summary>Creates a new instance of EnumInfo</summary>
		/// <param name="culture"></param>
		/// <param name="enum"></param>
		/// <param name="value"></param>
		/// <param name="name"></param>
		/// <param name="description"></param>
		/// <param name="htmlDescription"></param>
		/// <param name="attributes"></param>
		/// <created author="laurentiu.macovei" date="Thu, 14 Apr 2011 05:35:43 GMT"/>
		public EnumInfo(CultureInfo culture, EnumType @enum, long value, string name,
			string description, IHtmlString htmlDescription, params Attribute[] attributes)
		{
			this.Culture = culture;
			this.Enum = @enum;
			this.Value = value;
			this.Name = name;
			this.Description = description;
			this.HtmlDescription = htmlDescription;
			this.Attributes = attributes;
		}


		#endregion Constructors


		#region Properties

		/// <summary>Gets the Culture</summary>
		/// <created author="laurentiu.macovei" date="Thu, 14 Apr 2011 05:35:44 GMT"/>
		public CultureInfo Culture { get; private set; }

		/// <summary>Gets the Enum</summary>
		/// <created author="laurentiu.macovei" date="Thu, 14 Apr 2011 05:35:44 GMT"/>
		public EnumType Enum { get; private set; }

		/// <summary>Gets the Value</summary>
		/// <created author="laurentiu.macovei" date="Thu, 14 Apr 2011 05:35:44 GMT"/>
		public dynamic Value { get; private set; }

		/// <summary>Gets the Name</summary>
		/// <created author="laurentiu.macovei" date="Thu, 14 Apr 2011 05:35:44 GMT"/>
		public string Name { get; private set; }

		/// <summary>Gets the Description</summary>
		/// <created author="laurentiu.macovei" date="Thu, 14 Apr 2011 05:35:44 GMT"/>
		public string Description { get; private set; }

		/// <summary>Gets the HtmlDescription</summary>
		/// <created author="laurentiu.macovei" date="Thu, 14 Apr 2011 05:35:44 GMT"/>
		public IHtmlString HtmlDescription { get; private set; }

		/// <summary>Gets the Attributes</summary>
		/// <created author="laurentiu.macovei" date="Thu, 14 Apr 2011 05:35:44 GMT"/>
		public Attribute[] Attributes { get; private set; }


		#endregion Properties


	}
	#endregion Class  EnumInfo

	#region EnumStorage Class
	/// <summary>The EnumStorage class</summary>
	/// <created author="laurentiu.macovei" date="Thu, 10 Mar 2011 01:08:46 GMT"/>
	private class EnumStorage<EnumType, Culture>
		where EnumType : struct, IComparable, IFormattable, IConvertible
		where Culture : CultureInfo
	{

		#region Fields


		#region Const

		public static readonly List<EnumInfo<EnumType>> NamesAndValues = new List<EnumInfo<EnumType>>();
		public static readonly Dictionary<EnumType, EnumInfo<EnumType>> NamesAndValuesDictionary = new Dictionary<EnumType, EnumInfo<EnumType>>();

		#endregion Const


		#endregion Fields


		#region Constructors

		/// <summary>Static constructor of EnumStorage</summary>
		/// <created author="laurentiu.macovei" date="Thu, 14 Apr 2011 05:35:45 GMT"/>
		static EnumStorage()
		{
			var type = typeof(EnumType);
			var cultureInfo = typeof(Culture).GetField("Instance", BindingFlags.Public | BindingFlags.Static).GetValue(null) as Culture;
			var emptyAttributes = new Attribute[0];
			foreach (var name in type.GetFields(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static).Select(f => f.Name))
			{
				var @enum = (EnumType)Enum.Parse(type, name);
				IHtmlString htmlDescription;
				string description = null;
				htmlDescription = null;
				Attribute[] attributes = emptyAttributes;

				MemberInfo[] memInfo = type.GetMember(name);

				if (memInfo != null && memInfo.Length > 0)
				{

					attributes = memInfo[0].GetCustomAttributes(false).OfType<Attribute>().ToArray();
					var da = attributes.OfType<global::DisplayNameAttribute>().FirstOrDefault();

					if (da != null)
					{
						description = da.DisplayName;
						htmlDescription = new HtmlString(description);
					}
					else
					{
						var displayAttribute = attributes.OfType<DisplayAttribute>().FirstOrDefault();
						if (displayAttribute != null)
						{
							dynamic htmlProspecText = displayAttribute.GetDescription();
							if (htmlProspecText is IHtmlString)
							{
								htmlDescription = htmlProspecText as IHtmlString;
								description = htmlDescription.ToHtmlString();
							}
							else
							{
								description = htmlProspecText == null ? null : htmlProspecText.ToString();
								htmlDescription = new HtmlString(description);
							}
						}
						else
						{
							var descriptionAttribute = attributes.OfType<DescriptionAttribute>().FirstOrDefault();

							if (descriptionAttribute != null)
							{
								description = descriptionAttribute.Description;
								htmlDescription = new HtmlString(description);
							}

						}
					}
				}
				if (description == null)
					description = @enum.ToString();
				if (htmlDescription == null)
					htmlDescription = new HtmlString(description);

				var enumInfo = new EnumInfo<EnumType>(cultureInfo,
					@enum,
					@enum.ToInt64(CultureInfo.InvariantCulture),
					name,
					description,
					htmlDescription,
					attributes
				);
				NamesAndValuesDictionary[@enum] = enumInfo;
				NamesAndValues.Add(enumInfo);
			}
		}


		#endregion Constructors


	}
	#endregion Class  EnumStorage

}